home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 8: LINUX Games
/
Linux Cubed Series 8 - LINUX Games.iso
/
games
/
amusemen
/
nolan-1.1
/
nolan.tar
/
nolan.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-04-25
|
12KB
|
501 lines
/*
* nolan.c --- interactive Nolan chart test for character displays
*
* by Eric S. Raymond <esr@snark.thyrsus.com>
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <assert.h>
#include "screen.h"
#define CONTINUE "Press any key to continue..."
#define FINISH "Press any key to finish..."
typedef int bool;
#define TRUE 1
#define FALSE 0
/***************************************************************************
*
* Quiz interpreter state variables
*
***************************************************************************/
#define MAXLABEL 16 /* maximum screens per program */
#define MAXNAME 12 /* maximum characters per screen label */
#define MAXQUES 10 /* maximum questions per screen */
#define MAXLINE 132 /* allow for 80 characters plus markup */
typedef struct
{
int y, x; /* location of question prompt */
char label[MAXNAME]; /* the question label */
}
ques_t;
static ques_t questions[MAXQUES]; /* current questions */
static int nquestions; /* number of labels */
static long found; /* user took this before */
static int people; /* count of people in scorefile */
static int lineno = 0; /* script line number */
static int row; /* current screen line */
static int score; /* current scale score */
typedef int (*intfunc)(); /* function returning integer */
/*
* Attribute stack handling. We have to maintain a stack of currently
* active attributes so we'll be able to handle nested highlight marks
* properly.
*/
#define ATTRIB_STACK 10 /* way more than we should ever need */
static int attribs[ATTRIB_STACK] = {'w'};
static int *ap = attribs;
static void highlight();
/* special next-screen values */
#define EXIT -1
#define NEXT -2
/***************************************************************************
*
* Variables and data specific to the Nolan chart interpretation
*
***************************************************************************/
#define USERFILE "nolan.sco"
/*
* These give the proper position of the user's asterisk in the 45-degree
* tilted grid of dots that is the Nolan chart.
*/
#define min(x, y) (((x) > (y)) ? (y) : (x))
#define OVER(p, e) min(10 - p, e) /* dots over */
#define DOWN(p, e) (20 - ((p) + (e))) /* lines down */
static int personal, economic; /* self-government scores */
static int down; /* countdown to user's chart line */
static int over; /* count of dots to skip */
static int scatter[11][11]; /* scattergram data */
/* political types --- values uable directly as highlights */
#define AUT 'm' /* Authoritarians */
#define CEN 'w' /* Centrists */
#define CON 'b' /* Conservatives */
#define LEF 'r' /* Left Liberals */
#define LIB 'g' /* Libertarians */
/*
* This array translates from grid position to political type.
* As usual, vertical index is personal self-government,
* horizontal is economic self-government.
*/
static char scatcolors[11][11] =
{
AUT, AUT, AUT, AUT, AUT, CON, CON, CON, CON, CON, CON,
AUT, AUT, AUT, AUT, AUT, CON, CON, CON, CON, CON, CON,
AUT, AUT, AUT, AUT, AUT, CON, CON, CON, CON, CON, CON,
AUT, AUT, AUT, AUT, AUT, CEN, CON, CON, CON, CON, CON,
AUT, AUT, AUT, AUT, CEN, CEN, CEN, CON, CON, CON, CON,
LEF, LEF, LEF, CEN, CEN, CEN, CEN, CEN, LIB, LIB, LIB,
LEF, LEF, LEF, LEF, CEN, CEN, CEN, LIB, LIB, LIB, LIB,
LEF, LEF, LEF, LEF, LEF, CEN, LIB, LIB, LIB, LIB, LIB,
LEF, LEF, LEF, LEF, LEF, LIB, LIB, LIB, LIB, LIB, LIB,
LEF, LEF, LEF, LEF, LEF, LIB, LIB, LIB, LIB, LIB, LIB,
LEF, LEF, LEF, LEF, LEF, LIB, LIB, LIB, LIB, LIB, LIB,
};
/***************************************************************************
*
* Initialization and wrapup
*
***************************************************************************/
/*ARGSUSED0*/
static void wrapquiz(int dummy)
/* end the quiz, either normally or due to signal */
{
endscreen();
exit(0);
}
static void initquiz(void)
/* make sure we clean up properly on exit */
{
(void) signal(SIGINT,wrapquiz);
(void) signal(SIGINT,wrapquiz);
(void) signal(SIGIOT,wrapquiz); /* for assert(3) */
if (signal(SIGQUIT,SIG_IGN) != SIG_IGN)
(void) signal(SIGQUIT,wrapquiz);
initscreen();
}
/***************************************************************************
*
* Utility functions
*
***************************************************************************/
#define CENTER(str) ((COLS - reallength(str)) / 2)
#define RIGHT(str) (COLS - reallength(str) - 1)
static int reallength(char *str)
/* compute the "real" length of a string, not counting @{}-constructs */
{
char *cp;
int i = 0;
for (cp = str; *cp; cp++)
if (cp[0] == '@' && (cp[1] == '@' || cp[1] == '}'))
{
i++;
cp++;
}
else if (cp[0] == '}')
continue;
else if (cp[0] == '@')
i -= 2;
else
i++;
return(i);
}
/***************************************************************************
*
* The quiz interpreter
*
***************************************************************************/
static void display(char *oline)
/* display text, doing highlighting and parsing question blocks */
{
char *cp, *ep, line[MAXLINE];
/*
* KLUGE ALERT:
*
* Avoid putting out a line feed on the last line, it may cause a scroll.
* Unfortunately, we have to copy the line to avoid modifying an immutable
* string.
*/
(void) strcpy(line, oline);
if (row == LINES - 1 && (cp = strchr(line, '\n')))
*cp = '\0';
over = 0;
for (cp = line; *cp; cp++)
{
if (cp[0] == '@' && (cp[1] == '@' || cp[1] == '}' || cp[1] == '%'))
{
++cp;
sputc(*cp);
}
else if (cp[0] == '}' && ap > attribs)
{
if (ap > attribs)
--ap;
sattrset(*ap);
}
else if (cp[0] == '%')
{
/* these allow us to display the user's scores numerically */
if (cp[1] == 'E')
{
scprintf("%-3d", economic * 10);
cp +=2;
}
else if (cp[1] == 'P')
{
scprintf("%-3d", personal * 10);
cp += 2;
}
else if (cp[1] == 'N')
{
scprintf("%-3d", people);
cp += 2;
}
else
sputc(*cp);
}
else if (down == 0 && cp[0] == '.')
{
if (over == OVER(personal, economic))
{
highlight();
sputc('*');
sattrset(*ap);
}
else
sputc(*cp);
over++;
}
else if (cp[0] != '@')
{
sputc(*cp);
}
else
{
ep = strchr(cp, '}');
if (cp[2] != '{')
{
endscreen();
(void) fprintf(stderr,
"quiz: bad markup syntax, line %d\n",
lineno);
exit(1);
}
if (ep == (char *)NULL)
{
endscreen();
(void) fprintf(stderr,
"quiz: no markup terminator, line %d\n",
lineno);
exit(1);
}
/* character span from cp+3 to ep-1 is {}-enclosed part */
if (attrok(cp[1]))
{
sattrset(cp[1]);
*++ap = cp[1];
cp += 2;
continue;
}
else if (cp[1] == 'q')
{
/* accumulate this question label and its location */
screenloc(
&questions[nquestions].y,
&questions[nquestions].x);
*ep = '\0';
(void) strcpy(questions[nquestions].label, cp + 3);
*ep = '}';
nquestions++;
/* skip this command in further parsing */
if (ep)
cp = ep;
}
else
{
endscreen();
(void) fprintf(stderr,
"quiz: bad markup letter %c, line %d\n",
cp[1], lineno);
exit(1);
}
}
}
row++;
down--;
}
static void doquestions(void)
/* interpret and ask the questions found by a screen parse */
{
int i, response;
for (i = 0; i < nquestions; i++)
{
do {
movecursor(questions[i].y, questions[i].x);
response = tolower(sgetch());
highlight();
sputc(response);
sattrset(*ap);
} while
(response != 'y' && response != 'm' && response != 'n');
if (response == 'y')
score += 2;
else if (response == 'm')
score += 1;
else if (response == 'n')
score += 0;
}
}
static void scattergram(void)
/* plot a scattergram from the response totals */
{
int p, e;
for (p = 10; p >= 0; p--)
{
sattrset('b');
scprintf("%-3d", p * 10);
sattrset('w');
sputs(" |");
for (e = 0; e <= 10; e++)
{
sattrset(scatcolors[e][p]);
if (scatter[e][p])
scprintf(" %4d ", scatter[e][p]);
else
sputs(" . ");
sattrset('w');
}
sputs("\n");
}
sputs(" +-----------------------------------------------------------------\n");
sattrset('r');
sputs(" 0 10 20 30 40 50 60 70 80 90 100\n");
sattrset('w');
}
static int screenend(int next, int *scoretype)
/* handle end-of-screen */
{
char *exitprompt = (next == EXIT) ? FINISH : CONTINUE;
doquestions();
if (scoretype)
*scoretype = score;
/*
* The -1 offset below copes with curses implementations that
* don't do the right thing to suppress autoscroll when putting
* a character into the last screen position.
*/
movecursor(LINES - 1, RIGHT(exitprompt) - 1);
highlight();
sputs(exitprompt);
sattrset(*ap);
(void) sgetch();
return(next);
}
/* ARGSUSED0 */
static void screen(char *name)
/* get ready for a new screen */
{
}
/***************************************************************************
*
* Quiz gets included here
* The problem: we want the convenience of a data-driven program (being
* able to easily change the script contents and its markup with a text
* editor). But...we *don't* want the script exposed to errors or malice.
* So --- we compile the script into executable code. No muss, no fuss,
* and the retrieval is faster.
* The only drawback is that it's now painful to modify the script and
* regenerate this program unless you have a UNIX to work with..
*
***************************************************************************/
static int dispatch(void);
#include "script.h"
#ifndef STANDOUT
#define STANDOUT 'y'
#endif
static void highlight()
/* start highlight --- define this here to pick up generated STANDOUT value */
{
sattrset(STANDOUT[0]);
}
static int dispatch(void)
/* dispatch on user's political type */
{
switch (scatcolors[personal][economic])
{
case AUT:
return(AUT_SCREEN);
case CON:
return(CON_SCREEN);
case CEN:
return(CEN_SCREEN);
case LEF:
return(LEF_SCREEN);
case LIB:
return(LIB_SCREEN);
}
return(NEXT);
}
/***************************************************************************
*
* Main sequence
*
***************************************************************************/
/*ARGSUSED0*/
main(int argc, char *argv[])
{
char buf[BUFSIZ], *user = username();
FILE *ufp = (FILE *)NULL;
long offset;
int screen;
static bool quizline(char *line);
/* see whether user has done this before */
found = -1;
offset = 0L;
if ((ufp = fopen(USERFILE, "r+")) != (FILE *)NULL)
{
int pers, eco;
while (fscanf(ufp, "%[^:]:%d:%d\n", buf, &pers, &eco) == 3)
{
if (strcmp(buf, user) == 0)
found = offset;
else
scatter[pers / 10][eco / 10]++;
offset = ftell(ufp);
people++;
}
}
/* set up the screen */
initquiz();
/* interpret the quiz */
screen = 0;
for (;;)
{
int next = (*screenfuncs[screen])();
if (next == EXIT)
break;
else if (next == NEXT)
screen++;
else
screen = next;
}
/* we're done */
endscreen();
/* record user's scores in case he/she takes the quiz again */
if (ufp)
{
if (found > -1)
(void) fseek(ufp, found, SEEK_SET);
(void) fprintf(ufp, "%s:%03d:%03d\n", user, personal*10, economic*10);
(void) fclose(ufp);
}
return(0);
}
/* nolan.c ends here */